Skip to content

Add Functions gRPC core helpers#282

Open
YunchuWang wants to merge 28 commits into
mainfrom
yunchuwang/functions-grpc-support
Open

Add Functions gRPC core helpers#282
YunchuWang wants to merge 28 commits into
mainfrom
yunchuwang/functions-grpc-support

Conversation

@YunchuWang

@YunchuWang YunchuWang commented Jun 29, 2026

Copy link
Copy Markdown
Member

What

Adds the minimal host-integration surface so Azure Functions can drive a single durable work item per invocation, without running the long-lived gRPC worker loop. This mirrors the Azure Functions consolidation done for Python in durabletask-python PR #155.

API

Two methods on TaskHubGrpcWorker:

  • processOrchestratorRequest(request: Uint8Array): Promise<Uint8Array>
  • processEntityBatchRequest(request: Uint8Array): Promise<Uint8Array>

Each deserializes a TaskHubSidecarService protobuf payload, executes one work item, and returns the serialized response. Host integrations own any transport encoding (for example base64); base64 stays out of the core SDK.

Design (aligned with Python #155)

Rather than refactoring the worker, these methods reuse the worker's existing internal execution path (_executeOrchestratorInternal / _executeEntityInternal) and pass an in-process CapturingSidecarStub that records the completion payload instead of sending it over gRPC. This is the same null-stub pattern Python uses (AzureFunctionsNullStub).

Deliberately NOT included (kept minimal)

  • No base64 protobuf helper module (transport detail belongs in the host).
  • No object-level execute* public methods (the byte processors are the single entry point).
  • No V2 EntityRequest host path (not present in Python fix: Reset customStatus on continue-as-new in InMemoryOrchestrationBackend #155; the worker's internal V2 handling for the DTS backend is unchanged).
  • No new client options; task-hub routing metadata continues to flow through the existing metadataGenerator.

Tests

packages/durabletask-js/test/functions-grpc-support.spec.ts covers both byte processors end to end.

YunchuWang and others added 4 commits June 29, 2026 15:30
Expose low-level protobuf codecs and single work-item execution helpers for Azure Functions Durable JS gRPC consolidation without adding Functions metadata support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Expose stable byte-oriented worker processing methods and endpoint/taskHub client options for Azure Functions Durable JS gRPC consolidation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the work-item-executor extraction and base64 protobuf helpers with a
minimal, Python-aligned implementation. processOrchestratorRequest and
processEntityBatchRequest now reuse the existing worker execution path via an
in-process capturing stub (mirroring durabletask-python PR #155's null-stub
pattern) instead of refactoring the worker. Removed the base64 helper module,
the object-level execute* methods, the V2 EntityRequest host path, and the
related exports and tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@YunchuWang YunchuWang marked this pull request as ready for review June 30, 2026 22:02
Copilot AI review requested due to automatic review settings June 30, 2026 22:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a minimal “single work-item” execution surface to the Durable Task JS worker so host integrations (e.g., Azure Functions) can execute one orchestrator/entity batch request per invocation without running the long-lived gRPC streaming loop. This aligns with the SDK’s role as a low-level Durable Task Scheduler (sidecar) client/worker implementation.

Changes:

  • Added byte-level helper methods on TaskHubGrpcWorker to process serialized OrchestratorRequest / EntityBatchRequest payloads and return serialized responses.
  • Introduced an in-process CapturingSidecarStub that captures completion payloads instead of sending them over gRPC.
  • Added end-to-end tests and README documentation for the new host-integration surface.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
README.md Documents the new low-level host integration APIs and clarifies the non–Durable Functions programming model scope.
packages/durabletask-js/test/functions-grpc-support.spec.ts Adds e2e-style tests covering the new byte processors for orchestrations and entities.
packages/durabletask-js/src/worker/task-hub-grpc-worker.ts Implements the new public byte-processing methods and the capturing sidecar stub.

Comment thread packages/durabletask-js/src/worker/task-hub-grpc-worker.ts
Comment thread packages/durabletask-js/src/worker/task-hub-grpc-worker.ts
Comment thread packages/durabletask-js/src/worker/task-hub-grpc-worker.ts
YunchuWang and others added 5 commits July 1, 2026 15:34
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ator

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t glue

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ease pipeline

- Add DurableOrchestrationContext/DurableEntityContext (context.df.* adapters) + wrapOrchestrator/wrapEntity
- Add RetryOptions, callHttp (throws), parentInstanceId; align newGuid/callSubOrchestrator with v3 signatures
- Add v3 client query-return types (DurableOrchestrationStatus, OrchestrationRuntimeStatus, EntityStateResponse, PurgeHistoryResult)
- Add client getStatus/getStatusAll/getStatusBy/readEntityState/purgeInstanceHistory/startNew/waitForCompletionOrCreateCheckStatusResponse
- Add deprecated DurableOrchestrationClient alias
- Wire host-provided maxGrpcMessageSizeInBytes into gRPC channel options (Python parity)
- Remove dead CapturingSidecarStub.abandonRequest field (keep required no-op method)
- Add durable-functions package to build/release pipelines
Comment thread packages/azure-functions-durable/CHANGELOG.md Outdated
Comment thread packages/azure-functions-durable/README.md
"dependencies": {
"@azure/functions": "^4.16.1",
"@grpc/grpc-js": "^1.14.4",
"@microsoft/durabletask-js": "0.3.0"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: remember to bump this to 0.4.0, and we need to release @microsoft/durabletask-js first

also confirm whether @azure/functions 4.16.1 has the extension changes required,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

YunchuWang and others added 7 commits July 7, 2026 16:25
Drop rootDir, outDir, include, and exclude since they are inherited from ./tsconfig.json. Keep only the baseUrl + paths override that points @microsoft/durabletask-js imports at the built dist output.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… Functions

- Introduced `EntityId` class for classic v3 entity identifiers.
- Enhanced `DurableEntityContext` to include `isNewlyConstructed` and `entityId` properties.
- Implemented `signalEntity` method in `DurableEntityContext` for signaling other entities.
- Updated `DurableOrchestrationContext` to track and set custom status.
- Added unit tests for new features and behaviors.
Collapse the 4.0.0-alpha.0 changelog to a single bullet and rewrite the README to focus on what the package supports and why it is needed, dropping the implementation-plan / phase-status / open-questions sections.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
YunchuWang and others added 4 commits July 8, 2026 15:42
…onContext type aliases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rator

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… context.log to classic orchestration context

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…3-compatible)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…InstanceHistoryBy (v3-compat)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@andystaples andystaples left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Two things ship here: (1) the advertised core gRPC byte-processors (processOrchestratorRequest / processEntityBatchRequest + CapturingSidecarStub) in task-hub-grpc-worker.ts, and (2) an entire new durable-functions@4.0.0-alpha.0 package (packages/azure-functions-durable) — the intended next-major replacement for Azure/azure-functions-durable-js v3.

The core helpers are clean, and reusing the existing execution path with an in-process capturing stub is a nice low-risk approach. The v3 compat adapters (context.df.* for orchestrations/entities and the client method surface) are genuinely broad and well documented.

My main concern is the "drop-in / run unchanged" framing in the README: this is a strong compatibility shim for the common method surface, but several heavily-used v3 patterns and the entire durable-client authoring model are not covered — and there's one silent-failure bug in the orchestrator adapter. Details are inline; grouped here:

Bug (please fix)

  • wrapOrchestrator picks classic-vs-native by parameter arity, which silently mis-runs a native single-arg orchestrator (async function*(ctx)) — the body never executes and no error is thrown.

Behavioral breaks vs v3 (worth closing or documenting)

  • createTimer returns a non-cancelable Task; the Task.any + timeoutTask.cancel() timeout pattern breaks, and the Task result shape changed (isCompleted/isFaulted/resultisComplete/isFailed/getResult()).
  • Orchestration/entity contexts no longer extend InvocationContext.
  • Missing top-level exports: DummyOrchestrationContext/DummyEntityContext (breaks users' tests), error types (DurableError/AggregatedError/TaskFailedError) for instanceof, ManagedIdentityTokenSource/TokenSource; the app.client.* durable-client trigger helpers and setExceptionPropertiesProvider are also gone.
  • Client deltas: getStatus returns | undefined and ignores showHistory; createCheckStatusResponse now requires a defined request; startNew drops the version option.

Nits

  • parseJson doesn't guard "" (JSON.parse("") throws).
  • Core: a version-mismatch abandon makes processOrchestratorRequest throw a generic error.
  • Spurious deserializeBinary reorder in the generated proto .d.ts.
  • engines.node >= 22 vs @types/node@^18; the Node-22 floor is a notable consumer bump.

Overall a promising foundation — I'd fix the arity bug and soften/annotate the compatibility claims before this reads as a transparent v3 replacement. Leaving this as comments (non-blocking).

* core-native orchestrator as `(ctx, input)` so it passes through.
*/
export function wrapOrchestrator(handler: TOrchestrator | ClassicOrchestrator): TOrchestrator {
if (typeof handler === "function" && handler.length === 1) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (high): classic-vs-native detection by arity silently mis-runs native single-arg orchestrators.

handler.length === 1 treats any one-parameter function as classic. But a core-native orchestrator written as async function*(ctx) { yield ctx.callActivity(...) } — single param, ignoring input, a very common style — also has arity 1. It gets wrapped and invoked as classic(classicCtx), where classicCtx is { df, log, ... }, so ctx.callActivity is undefined.

Worse: because isGenerator() (line 257) checks for [Symbol.iterator], it returns false for the async generator the handler returns, so the wrapper returns the un-iterated generator and the engine completes the orchestration immediately with garbage output — the body never runs and nothing throws. (Note functions-grpc-support.spec.ts itself writes a native orchestrator as single-arg async (_ctx) => ....)

Suggest detecting by generator kind instead of arity, which matches the engine's actual async-iterator gate: handler.constructor.name === "GeneratorFunction" ⇒ classic (v3 sync generators); AsyncGeneratorFunction / async ⇒ native pass-through. Please also add a round-trip test that drives a native single-arg orchestrator through wrapOrchestrator → the real executor.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 8f62a35 — refined: classic vs core-native orchestrators are now detected by generator/async kind instead of pure arity (async function*/async => core-native pass-through; sync function* => classic, wrapped; plain sync fn => arity fallback), and the wrapper drives both sync and async generators via yield*. Added end-to-end regression tests through the real core executor: single-arg native async-generator body actually runs and yields a task; classic single-arg sync generator still works; classic non-generator return value still works.

}

/** Creates a durable timer that fires at the specified time. */
createTimer(fireAt: Date | number): Task<unknown> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break: createTimer returns a non-cancelable core Task, so the canonical durable-timeout pattern no longer works.

v3 createTimer returned a TimerTask with .cancel() / .isCanceled. The ubiquitous pattern

const timeout = context.df.createTimer(deadline);
const work = context.df.callActivity("DoWork");
const winner = yield context.df.Task.any([timeout, work]);
if (!timeout.isCompleted) timeout.cancel();

breaks two ways: core Task has no cancel() (→ TypeError, or a leaked timer that keeps the instance alive until it fires), and the result shape changed (isCompleted/isFaulted/resultisComplete/isFailed/getResult()), so timeout.isCompleted is always undefined. This is one of the most common DF patterns — consider surfacing a cancelable timer wrapper, or calling it out prominently in the migration notes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 8f62a35 — createTimer's return type is now TimerTask (core already returns a cancelable TimerTask via #293; only the type was hidden as Task). Added a test covering .cancel()/.isCanceled type visibility plus the Task.any(timer, work) then timer.cancel() timeout-race pattern.

}

/** The object passed to a classic (v3) orchestrator function; its `df` is the durable context. */
export interface ClassicOrchestrationContext {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break: the classic context no longer extends InvocationContext.

v3's OrchestrationContext extends InvocationContext, so orchestrators could read context.invocationId, context.functionName, context.extraInputs, etc. This interface exposes only df + log methods, so any orchestrator touching those members won't compile/run. Same applies to ClassicEntityContext in entity-context.ts (just { df }). Worth documenting as a breaking change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 8f62a35 (doc-only) — CHANGELOG + README migration sections now record the breaking change: classic orchestration context exposes only df + replay-safe log helpers (no invocationId/functionName/extraInputs), and the classic entity context is just { df }. Added the rationale (reading InvocationContext members inside an orchestrator is replay-nondeterministic and was never recommended). No members restored.

this.grpcHttpClientTimeout = config.grpcHttpClientTimeout;
}

createCheckStatusResponse(request: HttpRequest, instanceId: string): HttpResponse {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtle break: v3's signature was createCheckStatusResponse(request: HttpRequest | undefined, instanceId) and fell back to the binding's baseUrl when request was undefined. This overload requires a defined request and will throw at request.url (in getInstanceStatusUrl) if called with undefined. Consider accepting HttpRequest | undefined to preserve the v3 contract.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 8f62a35 — confirmed v3-faithful: createCheckStatusResponse(request: HttpRequest | undefined, instanceId) already accepts an undefined request and falls back to the durable-client binding's baseUrl to build the management payload; undefined-request test is in place. One nuance to flag: the compat layer reconstructs the status URL from baseUrl rather than rewriting the binding's managementUrls templates (behavior-equivalent for the status link on the consolidated path).

* @param options - When `showInput` is `false`, input/output payloads are not fetched.
* @returns The instance status, or `undefined` if the instance does not exist.
*/
async getStatus(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtle break: the return type is DurableOrchestrationStatus | undefined where v3 returned a non-optional DurableOrchestrationStatus, so existing callers doing (await getStatus(id)).runtimeStatus now get a TS error / possible runtime throw. Also GetStatusOptions.showHistory / showHistoryOutput are silently ignored and history is never populated (only showInput is honored). Worth documenting the reduced option set — and note startNew similarly drops v3's version option.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 8f62a35 — getStatus now matches v3: returns a non-optional DurableOrchestrationStatus and throws when the instance is missing (verified against v3 DurableClient.getStatus, which throws on the extension's 404). showHistory populates history from core getOrchestrationHistory; startNew forwards the v3 version option to the core scheduler (scheduleNewOrchestration supports version). Honest boundaries (documented in JSDoc + CHANGELOG/README): history entries are core HistoryEvent shape, not the .NET extension's history shape (gRPC path bypasses the extension HTTP formatter); showHistoryOutput is accepted but can't strip payloads (core events always carry them); showInput maps to core fetchPayloads, so showInput:false also gates output/customStatus (v3 omitted only input).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

further refined in 900064f — getStatus/startNew now implement all three v3 points (not just documented):

(a) not-found: verified v3 DurableClient.getStatus (src/durableClient/DurableClient.ts) — its case 404 throws and the return type is non-optional Promise. Replicated: returns non-optional status, throws a 'DurableClient error: No orchestration instance with ID ... was found.' Error when the instance is missing.

(b) showHistory/showHistoryOutput: showHistory populates history via core getOrchestrationHistory; showHistoryOutput toggles per-entry payloads (strips input/result when falsy, keeps them when true). v3 types history as Array (types/orchestration.d.ts), so entries are core HistoryEvent[]; the .NET extension's PascalCase HTTP history shape isn't reproduced because the gRPC path bypasses that formatter. showInput now gates only the top-level input (output/customStatus always returned, matching v3).

(c) version: startNew forwards options.version to core scheduleNewOrchestration (StartOrchestrationOptions.version).

Tests updated: not-found throw, showInput gates input only, showHistoryOutput strip vs keep, startNew version. build + lint + compat unit (81/81) green.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not-found message aligned with v3 in 0dcc47c — mirrors v3 DurableClient.getStatus's exact sentence: 'This usually means we could not find any data associated with the instanceId provided: .' Only v3's HTTP-404-specific first sentence is replaced (the gRPC path has no HTTP 404). Behavior is otherwise unchanged from 900064f: non-optional return + throw on not-found, showHistory/showHistoryOutput, showInput gates only input, startNew forwards version.


/** @hidden */
function parseJson(value: string | undefined): unknown {
return value === undefined ? undefined : JSON.parse(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: parseJson guards undefined but not "". JSON.parse("") throws, so a present-but-empty serialized payload would blow up getStatus / toDurableOrchestrationStatus. Normal flow yields undefined (absent StringValue wrapper) so this is low-risk, but cheap to harden: if (!value) return undefined;.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce -- empty-string already guarded; now also tolerant of non-JSON serializedOutput

export { EntityId } from "./entity-id";
export { EntityStateResponse } from "./entity-state-response";
export { PurgeHistoryResult } from "./purge-history-result";
export { OrchestrationFilter } from "./orchestration-filter";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break: several v3 top-level exports are missing, so import { ... } from "durable-functions" breaks for:

  • DummyOrchestrationContext / DummyEntityContext — v3 testing utilities; users' existing unit tests won't compile.
  • TaskFailedError (and v3's DurableError / AggregatedError) — needed for instanceof guards on caught orchestration errors; core throws its own TaskFailedError, which isn't re-exported here.
  • ManagedIdentityTokenSource / TokenSource.

Relatedly, the app surface drops the v3 durable-client trigger helpers (df.app.client.http / timer / storageQueue / ...) and setExceptionPropertiesProvider. The replacement model (generic app.http + df.input.durableClient() + df.getClient(context)) is reasonable, but it changes client-function handler signatures and should be documented as a breaking migration.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

const stub = new CapturingSidecarStub();
await this._executeOrchestratorInternal(req, "", stub as unknown as stubs.TaskHubSidecarServiceClient);

if (!stub.orchestratorResponse) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor (core): if versioning is enabled and a version mismatch resolves to abandon, _executeOrchestratorInternal calls the stub's no-op abandonTaskOrchestratorWorkItem and returns without setting a response — so this throws a generic "Orchestrator execution did not produce a response.". Unreachable via the Functions path today (the worker is constructed without versioning), but since this is a public, reusable entry point, consider distinguishing "abandoned" from a genuine failure, or documenting that abandon isn't supported on the single-work-item path.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

static serializeBinaryToWriter(message: OrchestratorRequest, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): OrchestratorRequest;
static deserializeBinaryFromReader(message: OrchestratorRequest, reader: jspb.BinaryReader): OrchestratorRequest;
static deserializeBinary(bytes: Uint8Array): OrchestratorRequest;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this looks like generated-proto churn — deserializeBinary / deserializeBinaryFromReader are merely reordered with no semantic change. Worth dropping from the diff to keep it focused (and confirm the proto codegen version isn't drifting).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

},
"homepage": "https://github.com/microsoft/durabletask-js/tree/main/packages/azure-functions-durable#readme",
"engines": {
"node": ">=22.0.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: engines.node >= 22 is a notable consumer-facing floor (drops the Node 18/20 that v3 supported) — worth calling out in the README/changelog. It's also inconsistent with @types/node@^18 in devDependencies.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in f935bce

… empty-string parseJson

Two self-contained fixes in the durable-functions v4 compat package:

- rewind(): delegate to core TaskHubGrpcClient.rewindInstance() instead of throwing 'rewind is not yet supported'; the optional reason defaults to an empty string.

- parseJson(): treat empty-string serialized input/output/customStatus as undefined so getStatusAll/getStatusBy no longer throw 'Unexpected end of JSON input'.

Adds one regression test per fix (durable-functions unit suite: 67 tests, all green).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
YunchuWang and others added 6 commits July 14, 2026 13:44
Brings the PR branch up to date with main so the compat layer can build against the current core public API: cancellable TimerTask (#293), native AggregateError WhenAll semantics (#302), rewind support (#296), and related fixes. Clean auto-merge (proto + worker).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolves andystaples' review comments plus a ListAll E2E bug on
packages/azure-functions-durable:

- #1 orchestrator arity: detect generator kind (sync=classic v3, async=core
  native) instead of an arity heuristic; driver now handles sync+async
  generators so single-param native orchestrators actually execute.
- #2 parseJson tolerates non-JSON serializedOutput (fixes getStatusAll 400).
- #3 createCheckStatusResponse accepts undefined request (baseUrl fallback).
- #4 createTimer returns TimerTask (cancelable) in its type signature.
- #5 re-export TaskFailedError from core + compat; document removed v3-only
  exports in CHANGELOG/README.
- #6/#7 document getStatus + classic-context breaking changes.
- #8 bump @types/node to ^22 to match engines.node>=22.
- #9 set version 0.4.0 + document release order.
- #10 distinct abandon error for the single work-item execution path.
- #11 revert proto codegen churn to origin/main.

Adds regression tests for items #1/#2/#3/#5/#10.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…, startNew version

- (1) orchestration-context: detect classic vs core-native orchestrators by generator/async kind (async* and async => native; sync generator => classic; plain sync => arity fallback) and drive both sync/async generators; add end-to-end regression tests through the real core executor.

- (2) createTimer already returns cancelable TimerTask (type now visible); add type/Task.any cancel test.

- (3) doc-only: classic contexts no longer extend InvocationContext (entity ctx is {df}); add replay-nondeterminism rationale to CHANGELOG/README.

- (4) createCheckStatusResponse already accepts undefined request with baseUrl fallback (v3-faithful).

- (5) getStatus returns non-optional DurableOrchestrationStatus and throws on not-found (v3 parity); showHistory populates core history; showInput maps to fetchPayloads; startNew forwards v3 version option. Docs note gRPC-path boundaries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- getStatus throws on not-found with a v3-style 'DurableClient error:' message (verified v3 DurableClient.getStatus throws on the extension's HTTP 404; returns non-optional DurableOrchestrationStatus).

- showInput now gates only the top-level input (payloads always fetched); output/customStatus are always returned, matching v3.

- showHistoryOutput now strips input/result payloads from history entries when falsy and keeps them when true. history stays core HistoryEvent[] (v3 types history as Array<unknown>).

- startNew forwards the v3 version option (already wired to core scheduleNewOrchestration).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
v3 DurableClient.getStatus throws on the extension's HTTP 404 with 'This usually means we could not find any data associated with the instanceId provided: <id>.'. Mirror that sentence verbatim (only the HTTP-404-specific first sentence is replaced, since the gRPC path has no HTTP 404). Update the not-found tests to assert the v3 message.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The single-work-item abandon path is JS-specific for the Azure Functions host integration. durabletask-python has no equivalent single-work-item helper, and its worker-loop abandon hands the item back over a real sidecar stub rather than no-opping, so the prior 'matches the Python provider, whose null stub no-ops' note was inaccurate. Comment-only change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants